#include "options.h"
#include "../base/logger.h"
#include <string.h>
#include <stdarg.h>

USE_JLIB_NS


char Options::__valid_tag (char tag) noexcept {
  if (tag < '0')
    return 0;
  if (tag > '9' && tag < 'A')
    return 0;
  if (tag > 'Z' && tag < '_')
    return 0;
  if (tag > '_' && tag < 'a')
    return 0;
  if (tag > 'z')
    return 0;
  return tag;
}

bool Options::__valid_name (const char* name) noexcept {
  if (!name || !*name || !__valid_tag(*name))
    return false;
  for (++name; *name; ++name)
    if (!__valid_tag(*name) && *name != '-')
      return false;
  return true;
}


void Options::_add_by_tag (char tag, const char* value) noexcept {
  auto idef = _tags.find(tag);
  if (idef == _tags.end()) {
    log_e("Console: Unknown tag -%c", tag);
    return;
  }
  auto& def = idef->second;
  auto name = std::string(def.name ? def.name : "");
  if (def.flag & NonValue) {
    _map_by_tag[tag];
    if (!name.empty())
      _map_by_name[name];
    if (value)
      _unnamed.emplace_back(value);
  }
  else if (def.flag & Required) {
    if (!value) {
      log_e("Console: Parameter -%c must have a value", tag);
      return;
    }
    _map_by_tag[tag].emplace_back(value);
    if (!name.empty())
      _map_by_name[name].emplace_back(value);
  }
  else if (value) {
    _map_by_tag[tag].emplace_back(value);
    if (!name.empty())
      _map_by_name[name].emplace_back(value);
  }
  else {
    _map_by_tag[tag];
    if (!name.empty())
      _map_by_name[name];
  }
}


void Options::_add_by_name (const std::string& name, const char* value) noexcept {
  auto idef = _names.find(name);
  if (idef == _names.end()) {
    log_e("Console: Unknown name --%s", name.c_str());
    return;
  }
  auto& def = idef->second;
  auto tag = def.tag;
  if (def.flag & NonValue) {
    _map_by_name[name];
    if (tag)
      _map_by_tag[tag];
    if (value)
      _unnamed.emplace_back(value);
  }
  else if (def.flag & Required) {
    if (!value) {
      log_e("Console: Parameter -%s must have a value", name.c_str());
      return;
    }
    _map_by_name[name].emplace_back(value);
    if (tag)
      _map_by_tag[tag].emplace_back(value);
  }
  else if (value) {
    _map_by_name[name].emplace_back(value);
    if (tag)
      _map_by_tag[tag].emplace_back(value);
  }
  else {
    _map_by_name[name];
    if (tag)
      _map_by_tag[tag];
  }
}

void Options::_add_unnamed (const char* value) noexcept {
  _unnamed.emplace_back(value);
}


void Options::_collect () noexcept {
  auto& tag = _tag_cache;
  auto& name = _name_cache;
  if (tag) {
    _add_by_tag(tag, nullptr);
    tag = 0;
  }
  else if (!name.empty()) {
    _add_by_name(name, nullptr);
    name = "";
  }
}

void Options::_handle_arg (const char* arg) noexcept {
  auto& tag = _tag_cache;
  auto& name = _name_cache;
  if (*arg == '-') {
    _collect();
    if (*++arg == '-') {
      auto value = strchr(++arg, '=');
      if (value) {
        if (value != arg) {
          auto len = value - arg;
          _add_by_name(std::string(arg, len), ++value);
        }
      }
      else name = std::string(arg);
    }
    else {
      if (!*arg) {
        log_e("Console:: Null options");
        return;
      }
      for (tag = __valid_tag(*arg++); *arg; tag = *arg++)
        _add_by_tag(tag, nullptr);
    }
  }
  else {
    if (tag) {
      _add_by_tag(tag, arg);
      tag = 0;
    }
    else if (!name.empty()) {
      _add_by_name(name, arg);
      name = "";
    }
    else {
      _add_unnamed(arg);
    }
  }
}


void Options::operator() (int argc, char** argv) noexcept {
  for (auto i = 0; i < argc; ++i)
    _handle_arg(argv[i]);
  _collect();
}

void Options::operator() (const std::vector<std::string>& args) noexcept {
  for (auto& arg : args)
    _handle_arg(arg.c_str());
  _collect();
}


bool Options::operator() (char tag) const noexcept {
  return _map_by_tag.find(tag) != _map_by_tag.end();
}

bool Options::operator() (const std::string& name) const noexcept {
  return _map_by_name.find(name) != _map_by_name.end();
}


const char* Options::operator[] (size_t index) const noexcept {
  if (index >= _unnamed.size())
    return nullptr;
  return _unnamed[index].c_str();
}

const char* Options::operator[] (char tag) const noexcept {
  auto iter = _map_by_tag.find(tag);
  if (iter == _map_by_tag.end())
    return nullptr;
  auto& values = iter->second;
  if (values.empty())
    return nullptr;
  return values[values.size() - 1].c_str();
}

const char* Options::operator[] (const std::string& name) const noexcept {
  auto iter = _map_by_name.find(name);
  if (iter == _map_by_name.end())
    return nullptr;
  auto& values = iter->second;
  if (values.empty())
    return nullptr;
  return values[values.size() - 1].c_str();
}


Options::Options (const std::initializer_list<Def>& defs) noexcept {
  for (auto& def : defs) {
    if (def.tag) {
      if (!__valid_tag(def.tag))
        log_e("Options: Invalid parameter tag");
      else
        _tags.emplace(def.tag, def);
    }
    if (def.name) {
      if (!__valid_name(def.name))
        log_e("Options: Invalid parameter name");
      else
        _names.emplace(def.name, def);
    }
  }
}

Options::Options (int argc, char** argv, const std::initializer_list<Def>& defs) noexcept : Options(defs) {
  operator()(argc, argv);
}

Options::Options (const std::vector<std::string>& args, const std::initializer_list<Def>& defs) noexcept : Options(defs) {
  operator()(args);
}

